<!DOCTYPE html>
<html lang="zh_cn">
<head>
          <title>来玩魔王的咚</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charset="utf-8" />
        <!-- twitter card metadata -->
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/images/mowang.png">
<meta name="twitter:site" content="">
<meta name="twitter:title" content="shell脚本编写技巧">
<meta name="twitter:description" content="<p>set设置：编写可靠的脚本；readonly：限制变量只读；flock：限制脚本单例执行；意外退出时kill所有子进程；timeout：限制运行时间；exit：退出状态码；IFS变量：字段分隔符；tee：同时打印并记录到文件；cat：脚本中创建文件；永久重定向;mktemp：创建本地临时文件</p>">
        <!-- OG Tags -->
<meta property="og:url" content="/shell-script-advanced.html"/>
<meta property="og:title" content="来玩魔王的咚 | shell脚本编写技巧" />
<meta property="og:description" content="<p>set设置：编写可靠的脚本；readonly：限制变量只读；flock：限制脚本单例执行；意外退出时kill所有子进程；timeout：限制运行时间；exit：退出状态码；IFS变量：字段分隔符；tee：同时打印并记录到文件；cat：脚本中创建文件；永久重定向;mktemp：创建本地临时文件</p>" />
        <!-- favicon -->
        <link rel="icon" type="image/png" href="/images/mowang.png">
        <!-- moment.js for date formatting -->
        <script src="/theme/js/moment.js"></script>
        <!-- css -->
        <link rel="stylesheet" type="text/css" href="/theme/css/main.css" />
        <!-- 左边的menu，如果页面高度不够，就跟着滚动，否则文章分类显示不全 -->
        <link rel="stylesheet" type="text/css" href="/theme/css/mycss/menu.css" />
		<script>
			
                /*! grunt-grunticon Stylesheet Loader - v2.1.2 | https://github.com/filamentgroup/grunticon | (c) 2015 Scott Jehl, Filament Group, Inc. | MIT license. */
    
    (function(e){function t(t,n,r,o){"use strict";function a(){for(var e,n=0;u.length>n;n++)u[n].href&&u[n].href.indexOf(t)>-1&&(e=!0);e?i.media=r||"all":setTimeout(a)}var i=e.document.createElement("link"),l=n||e.document.getElementsByTagName("script")[0],u=e.document.styleSheets;return i.rel="stylesheet",i.href=t,i.media="only x",i.onload=o||null,l.parentNode.insertBefore(i,l),a(),i}var n=function(r,o){"use strict";if(r&&3===r.length){var a=e.navigator,i=e.Image,l=!(!document.createElementNS||!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")||e.opera&&-1===a.userAgent.indexOf("Chrome")||-1!==a.userAgent.indexOf("Series40")),u=new i;u.onerror=function(){n.method="png",n.href=r[2],t(r[2])},u.onload=function(){var e=1===u.width&&1===u.height,a=r[e&&l?0:e?1:2];n.method=e&&l?"svg":e?"datapng":"png",n.href=a,t(a,null,null,o)},u.src="",document.documentElement.className+=" grunticon"}};n.loadCSS=t,e.grunticon=n})(this);(function(e,t){"use strict";var n=t.document,r="grunticon:",o=function(e){if(n.attachEvent?"complete"===n.readyState:"loading"!==n.readyState)e();else{var t=!1;n.addEventListener("readystatechange",function(){t||(t=!0,e())},!1)}},a=function(e){return t.document.querySelector('link[href$="'+e+'"]')},c=function(e){var t,n,o,a,c,i,u={};if(t=e.sheet,!t)return u;n=t.cssRules?t.cssRules:t.rules;for(var l=0;n.length>l;l++)o=n[l].cssText,a=r+n[l].selectorText,c=o.split(");")[0].match(/US\-ASCII\,([^"']+)/),c&&c[1]&&(i=decodeURIComponent(c[1]),u[a]=i);return u},i=function(e){var t,o,a;o="data-grunticon-embed";for(var c in e)if(a=c.slice(r.length),t=n.querySelectorAll(a+"["+o+"]"),t.length)for(var i=0;t.length>i;i++)t[i].innerHTML=e[c],t[i].style.backgroundImage="none",t[i].removeAttribute(o);return t},u=function(t){"svg"===e.method&&o(function(){i(c(a(e.href))),"function"==typeof t&&t()})};e.embedIcons=i,e.getCSS=a,e.getIcons=c,e.ready=o,e.svgLoadedCallback=u,e.embedSVG=u})(grunticon,this);
                
                grunticon(["/theme/css/icons.data.svg.css", "/theme/css/icons.data.png.css", "/theme/css/icons.fallback.css"]);
            </script>
        <noscript><link href="/theme/css/icons.fallback.css" rel="stylesheet"></noscript>
        <!-- menu toggle javascript -->
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", initMenu);
            
            function initMenu(){
                var menu = document.getElementById("menu");
                var menulink = document.getElementById("menu-link");
                menulink.addEventListener("click", function toggleMenu(){
                        window.event.preventDefault();
                        menulink.classList.toggle('active');
                        menu.classList.toggle('active');              
                    });
            };
        </script>
        <!-- 不蒜子 -->
        <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

    <meta name="description" content="<p>set设置：编写可靠的脚本；readonly：限制变量只读；flock：限制脚本单例执行；意外退出时kill所有子进程；timeout：限制运行时间；exit：退出状态码；IFS变量：字段分隔符；tee：同时打印并记录到文件；cat：脚本中创建文件；永久重定向;mktemp：创建本地临时文件</p>" />

    <meta name="tags" content="shell" />
    <meta name="tags" content="进程锁" />
  <!-- 替换部分base的样式，看文章时，要再宽一点，右边有很多空间可以撑开 -->
  <link rel="stylesheet" type="text/css" href="/theme/css/mycss/article.css" />

</head>
<body>
    <div role="banner" id="masthead">
        <header>
            <a href="/"><img src="/images/mowang.png" alt="McManus Logo"></a>
                <h1>来玩魔王的咚@骑士救兵</h1>
            <a href="#menu" id="menu-link">more stuff</a>
            <nav id="menu">
                <ul>
                        <li><a href="/tags">tags</a></li>
                            <li><a href="/category/cloud.html">Cloud</a></li>
                            <li><a href="/category/docker.html">Docker</a></li>
                            <li><a href="/category/go.html">Go</a></li>
                            <li class="active"><a href="/category/linux.html">Linux</a></li>
                            <li><a href="/category/python.html">Python</a></li>
                            <li><a href="/category/xue-xi-bi-ji.html">学习笔记</a></li>
                            <li><a href="/category/yun-wei-zi-dong-hua.html">运维自动化</a></li>
                </ul>
            </nav>
        </header>
    </div>
        <div class="page" role="main">
  <div class="article" role="article">
    <article>
        <footer>
            <a name="top"></a>
            <p>
              <time datetime=" 2020-08-16 15:10:00+08:00">
                <script>document.write(moment('2020-08-16 15:10:00+08:00').format('LL'));</script>
              </time>
              ~
              <time datetime=" 2020-08-16 15:10:00+08:00">
                <script>document.write(moment('2020-08-17 11:30:00+08:00').format('LL'));</script>
              </time>
            </p>
        </footer>
        <header>
          <h2>
            shell脚本编写技巧
          </h2>
        </header>
      <div class="content">
         <div class="toc">
<ul>
<li><a href="#set-xeuo-pipefail">set -xeuo pipefail</a><ul>
<li><a href="#set-x">set -x</a></li>
<li><a href="#set-e">set -e</a></li>
<li><a href="#set-u">set -u</a></li>
<li><a href="#set-o-pipefail">set -o pipefail</a></li>
<li><a href="#guan-bi-xuan-xiang">关闭选项</a></li>
<li><a href="#set-ming-ling-shuo-ming">set 命令说明</a></li>
</ul>
</li>
<li><a href="#xian-zhi-bian-liang-zhi-du-readonly">限制变量只读(readonly)</a></li>
<li><a href="#xian-zhi-jiao-ben-dan-li-zhi-xing">限制脚本单例执行</a></li>
<li><a href="#yi-wai-tui-chu-shi-kill-suo-you-zi-jin-cheng">意外退出时kill所有子进程</a></li>
<li><a href="#timeout-xian-zhi-yun-xing-shi-jian">timeout 限制运行时间</a></li>
<li><a href="#tui-chu-zhuang-tai-ma">退出状态码</a><ul>
<li><a href="#cha-kan-tui-chu-zhuang-tai-ma">查看退出状态码</a></li>
<li><a href="#exit-ming-ling">exit 命令</a></li>
</ul>
</li>
<li><a href="#ifs-zi-duan-fen-ge-fu">IFS 字段分隔符</a><ul>
<li><a href="#cha-kan-bian-liang-mo-ren-zhi">查看变量默认值</a></li>
<li><a href="#lin-shi-geng-gai-ifs">临时更改IFS</a></li>
<li><a href="#shi-yong-chang-jing">使用场景</a></li>
<li><a href="#zhi-ding-duo-ge-ifs-zi-fu">指定多个IFS字符</a></li>
</ul>
</li>
<li><a href="#tee-jiang-shu-chu-tong-shi-fa-song-dao-ping-mu-he-wen-jian">tee 将输出同时发送到屏幕和文件</a><ul>
<li><a href="#xie-ru-dao-wen-jian">写入到文件</a></li>
</ul>
</li>
<li><a href="#jiao-ben-zhong-chuang-jian-wen-jian-cat-eof">脚本中创建文件（cat EOF）</a><ul>
<li><a href="#ji-ben-yong-fa">基本用法</a></li>
<li><a href="#-eof">&lt;&lt;- EOF</a></li>
<li><a href="#zhi-jie-chuang-jian-jiao-ben-wen-jian-zhi-xing">直接创建脚本文件执行</a></li>
</ul>
</li>
<li><a href="#yong-jiu-zhong-ding-xiang">永久重定向</a><ul>
<li><a href="#exec-ming-ling">exec 命令</a></li>
<li><a href="#bao-cun-zhong-ding-xiang-wen-jian-miao-shu-fu">保存重定向文件描述符</a></li>
</ul>
</li>
<li><a href="#chuang-jian-ben-di-lin-shi-wen-jian-mktemp">创建本地临时文件（mktemp）</a><ul>
<li><a href="#chuang-jian-wen-jian">创建文件</a></li>
<li><a href="#zai-jiao-ben-zhong-shi-yong">在脚本中使用</a></li>
<li><a href="#zai-tmp-mu-lu-chuang-jian-lin-shi-wen-jian">在/tmp目录创建临时文件</a></li>
<li><a href="#chuang-jian-lin-shi-mu-lu">创建临时目录</a></li>
</ul>
</li>
</ul>
</div>
<p>写shell脚本的坑很多，它并不是一个很严谨的语言，很多时候也是有替代方案的。但是系统默认支持，直接能用，完成一些简单的需求确实很方便。<br>
这篇是一些写脚本的技巧，可以避开一些坑。  </p>
<h3 id="set-xeuo-pipefail"><a class="toclink" href="#set-xeuo-pipefail">set -xeuo pipefail</a></h3>
<p>建议在每个脚本的开头都先写上一句set：</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span> -xeuo pipefail
</code></pre></div>

<p>加了这些设置以后，可以避免很多问题，能让一些隐藏的问题暴露出来。让你的脚本更可靠。  </p>
<p>如果是已经编写好的脚本，没有按这个规范写，还有一个不用修改脚本的办法。<br>
在执行脚本的时候，从命令行传入这些设置：</p>
<div class="highlight"><pre><span></span><code>$ bash -xeuo pipefail script.sh
</code></pre></div>

<p>这里一个4个参数，分别有不同的作用。下面一个个讲。  </p>
<h4 id="set-x"><a class="toclink" href="#set-x">set -x</a></h4>
<p>这个是可选项。在执行每一个命令之前，会把经过被bash处理之后的内容先打印出来。命令的别名、变量等都会展开为实际的内容。<br>
设置后的效果：</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">set</span> -x
++ <span class="nb">printf</span> <span class="s1">&#39;\033]0;%s@%s:%s\007&#39;</span> root Playhost <span class="s1">&#39;~&#39;</span>
$ ll
+ ls --color<span class="o">=</span>auto -l --color<span class="o">=</span>auto
总用量 <span class="m">4</span>
-rw-------. <span class="m">1</span> root root <span class="m">1615</span> 4月   <span class="m">7</span> <span class="m">12</span>:59 anaconda-ks.cfg
++ <span class="nb">printf</span> <span class="s1">&#39;\033]0;%s@%s:%s\007&#39;</span> root Playhost <span class="s1">&#39;~&#39;</span>
$ <span class="nb">echo</span> <span class="nv">$SHELL</span>
+ <span class="nb">echo</span> /bin/bash
/bin/bash
++ <span class="nb">printf</span> <span class="s1">&#39;\033]0;%s@%s:%s\007&#39;</span> root Playhost <span class="s1">&#39;~&#39;</span>
$ <span class="nb">set</span> +x
+ <span class="nb">set</span> +x
$ 
</code></pre></div>

<p>加了这个设置，会有额外的输出。debug的时候可能有点用，正式运行的脚本可以不用加这个。  </p>
<h4 id="set-e"><a class="toclink" href="#set-e">set -e</a></h4>
<p>如果遇到一个指令失败（返回值非零），立即退出。<br>
默认的情况下，shell只是顺序执行脚本的命令，即使命令执行失败，也会继续执行之后的命令。<br>
加上这个设置后，只要遇到执行失败的命令，就会退出脚本，避免脚本继续执行。<br>
如果直接在命令行中测试，则会退出当前会话：</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">set</span> -e
$ ls <span class="m">123</span>
ls: 无法访问123: 没有那个文件或目录
</code></pre></div>

<p><strong>例外</strong><br>
有的时候，有些命令的失败需要忽略，可以加上|| true，像下面这样：</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">set</span> -e
$ ls <span class="m">123</span> <span class="o">||</span> <span class="nb">true</span>
ls: 无法访问123: 没有那个文件或目录
$ 
</code></pre></div>

<p>另外，还有别的例外情况，就是管道。这个在后面的<code>-o pipefail</code>的参数介绍中单独再讲。  </p>
<h4 id="set-u"><a class="toclink" href="#set-u">set -u</a></h4>
<p>试图使用未定义的变量，就报错。<br>
如果再bash中使用一个未定义的变量，默认会展开为一个空字符串。<br>
看下这个简单的脚本：</p>
<div class="highlight"><pre><span></span><code><span class="nv">MYDIR</span><span class="o">=</span>/tmp
rm -rf <span class="nv">$MYDIR</span>/*
</code></pre></div>

<p>删除某个目录下所有的文件，似乎没啥问题。如果MYDIR变量因为某些原因没有被赋值（比如变量名打错了），那就变成删除根目录了。不只是笔误，其他比如环境的变化，导致某些变量原本有值，之后变了，也可能会出现类似情况。总之，可能会是一个十分危险的隐患，需要尽量避免。  </p>
<p><strong>例外</strong><br>
有时候，如果需要某个变量未赋值就取空字符串，可以用这样的写法<code>${MYDIR:-}</code>。<em>如果为空，就用后面的缺省值。这里-后面没任何内容，就是空值。</em><br>
这个是<strong>bash参数展开</strong>(Parameter Expansion)的语法，这里不详细讨论。  </p>
<p>设置后的效果：</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">echo</span> <span class="nv">$PATH</span>
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
$ <span class="nb">echo</span> <span class="nv">$PATH1</span>

$ <span class="nb">set</span> -u
$ <span class="nb">echo</span> <span class="nv">$PATH1</span>
-bash: PATH1: 为绑定变量
$ <span class="nb">echo</span> <span class="si">${</span><span class="nv">PATH1</span><span class="k">:-</span><span class="si">}</span>

$ <span class="nb">set</span> +u
$ 
</code></pre></div>

<p>遇到使用未设置的变量会报错，配合上面的-e，失败立即退出，就会立即退出了。  </p>
<h4 id="set-o-pipefail"><a class="toclink" href="#set-o-pipefail">set -o pipefail</a></h4>
<p>之前已经用了-e参数来确保命令失败后直接退出。但是还有例外的情况。默认情况下，用管道串联的命令，只有最后一条命令失败时才会退出。<br>
所谓管道命令，就是多个子命令通过管道运算符（|）组合成为一个大的命令。Bash 会把最后一个子命令的返回值，作为整个命令的返回值。也就是说，只要最后一个子命令不失败，管道命令总是会执行成功，因此它后面命令依然会执行，set -e就失效了。<br>
解决的办法就是再加上<code>-o pipefail</code>这个设置。  </p>
<p>设置前后的效果：</p>
<div class="highlight"><pre><span></span><code>$ foo <span class="p">|</span> <span class="nb">echo</span> a
a
-bash: foo: 未找到命令
$ <span class="nb">echo</span> <span class="nv">$?</span>
<span class="m">0</span>
$ <span class="nb">set</span> -o pipefail
$ foo <span class="p">|</span> <span class="nb">echo</span> a
a
-bash: foo: 未找到命令
$ <span class="nb">echo</span> <span class="nv">$?</span>
<span class="m">127</span>
$ <span class="nb">set</span> +o pipefail
$ 
</code></pre></div>

<h4 id="guan-bi-xuan-xiang"><a class="toclink" href="#guan-bi-xuan-xiang">关闭选项</a></h4>
<p>上面的每一个选，默认都是关闭的。然后我们用了-来打开特定的选项。遇到一些特殊的情况，也提供了相应的例外处理的方法。<br>
也可以把打开的选项关闭掉，等处理完例外的情况，再重新打开。<br>
要关闭某个选项，把-替换成+就可以了:</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">set</span> -e
$ ls <span class="m">123</span> <span class="o">||</span> <span class="nb">true</span>
ls: 无法访问123: 没有那个文件或目录
$ <span class="nb">set</span> +e
$ ls <span class="m">123</span>
ls: 无法访问123: 没有那个文件或目录
$ <span class="nb">set</span> -e
</code></pre></div>

<p>这里就是给出另一个解决例外情况的思路。  </p>
<h4 id="set-ming-ling-shuo-ming"><a class="toclink" href="#set-ming-ling-shuo-ming">set 命令说明</a></h4>
<p>set命令用于设置shell，能设置所使用shell的执行方式，实现一些需求。下面列出了其他的一些参数。<br>
参数说明：</p>
<ul>
<li>-a: 标示已修改的变量，以供输出至环境变量。</li>
<li>-b: 使被中止的后台程序立刻回报执行状态。</li>
<li>-C: 转向所产生的文件无法覆盖已存在的文件。</li>
<li>-d: Shell预设会用杂凑表记忆使用过的指令，以加速指令的执行。使用-d参数可取消。</li>
<li>-e: 若指令传回值不等于0，则立即退出shell。</li>
<li>-f: 取消使用通配符。</li>
<li>-h: 自动记录函数的所在位置。</li>
<li>-H: Shell可利用"!"加&lt;指令编号&gt;的方式来执行history中记录的指令。</li>
<li>-k: 指令所给的参数都会被视为此指令的环境变量。</li>
<li>-l: 记录for循环的变量名称。</li>
<li>-m: 使用监视模式。</li>
<li>-n: 只读取指令，而不实际执行。</li>
<li>-p: 启动优先顺序模式。</li>
<li>-P: 启动-P参数后，执行指令时，会以实际的文件或目录来取代符号连接。</li>
<li>-t: 执行完随后的指令，即退出shell。</li>
<li>-u: 当执行时使用到未定义过的变量，则显示错误信息。</li>
<li>-v: 显示shell所读取的输入值。</li>
<li>-x: 执行指令后，会先显示该指令及所下的参数。</li>
<li>+&lt;参数&gt;: 取消某个set曾启动的参数。</li>
<li>-o option-name: 有很多option-name可选，上面的短参数都有一个对应的option-name，效果是一样的。不过pipefail没有对应的短参数，只能用这个方法设置。</li>
</ul>
<h3 id="xian-zhi-bian-liang-zhi-du-readonly"><a class="toclink" href="#xian-zhi-bian-liang-zhi-du-readonly">限制变量只读(readonly)</a></h3>
<p>通常会在脚本中定义一下变量，有很多是定义后不需要修改的。为了避免这些变量被意外修改，可以将他们定义为只读。  </p>
<p>定义变量的同时，设置只读：</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">readonly</span> <span class="nv">k1</span><span class="o">=</span>value1
$ <span class="nb">echo</span> <span class="nv">$k1</span>
value1
$ <span class="nv">k1</span><span class="o">=</span>value1b
-bash: k1: 只读变量
$ 
</code></pre></div>

<p>定义变量之后，在设置只读：</p>
<div class="highlight"><pre><span></span><code>$ <span class="nv">k2</span><span class="o">=</span>value2
$ <span class="nb">readonly</span> k2
$ <span class="nb">echo</span> <span class="nv">$k2</span>
value2
$ <span class="nv">k2</span><span class="o">=</span>value2b
-bash: k2: 只读变量
$ 
</code></pre></div>

<p>另外，设置只读后是非常严格的，没有找到任何常规手段可以进行反向操作：</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">readonly</span> <span class="nv">k3</span><span class="o">=</span>value3
$ <span class="nb">unset</span> k3
-bash: unset: k3: 无法反设定: 只读 variable
$ 
</code></pre></div>

<p>好处是，可以放心的使用只读变量。坏处是，牺牲的灵活性。  </p>
<h3 id="xian-zhi-jiao-ben-dan-li-zhi-xing"><a class="toclink" href="#xian-zhi-jiao-ben-dan-li-zhi-xing">限制脚本单例执行</a></h3>
<p>使用了flock命令，确保同一时间，脚本只能启动一个。关于flock详细的内容我写在下一篇了：<br>
<a href="https://steeed.gitee.io/lock-program-by-file.html">https://steeed.gitee.io/lock-program-by-file.html</a>  </p>
<p>crontab中的用法：</p>
<div class="highlight"><pre><span></span><code>$ crontab -l
*/5 * * * * flock -xn filename.lock /bin/bash myscrit.sh <span class="p">&amp;</span>&gt;/dev/null
</code></pre></div>

<p>确保前一次脚本没有执行完成，下一轮的时候不会再启动一个新的。  </p>
<p>直接在脚本内限制，把下面的一行代码放在脚本开始的部分：</p>
<div class="highlight"><pre><span></span><code><span class="o">[</span> <span class="s2">&quot;</span><span class="si">${</span><span class="nv">FLOCKER</span><span class="si">}</span><span class="s2">&quot;</span> !<span class="o">=</span> <span class="s2">&quot;</span><span class="nv">$0</span><span class="s2">&quot;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">exec</span> env <span class="nv">FLOCKER</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$0</span><span class="s2">&quot;</span> flock -en <span class="s2">&quot;</span><span class="nv">$0</span><span class="s2">&quot;</span> <span class="s2">&quot;</span><span class="nv">$0</span><span class="s2">&quot;</span> <span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span> <span class="o">||</span> :
</code></pre></div>

<p>有了上面这一行，脚本同时只能启动一个。  </p>
<h3 id="yi-wai-tui-chu-shi-kill-suo-you-zi-jin-cheng"><a class="toclink" href="#yi-wai-tui-chu-shi-kill-suo-you-zi-jin-cheng">意外退出时kill所有子进程</a></h3>
<p>在脚本中启动的子脚本、子进程，当父脚本意外退出时，子进程并不会退出，而是继续运行。<br>
可以利用trap命令在脚本退出时，kill掉整个进程组。把下面的代码放到脚本开头：</p>
<div class="highlight"><pre><span></span><code><span class="nb">trap</span> <span class="s2">&quot;trap - SIGTERM &amp;&amp; kill -- -</span><span class="nv">$$</span><span class="s2">&quot;</span> SIGINT SIGTERM EXIT
</code></pre></div>

<p><strong>例外</strong><br>
父进程如果是被强制关闭（kill -9）的话，就无效了。因为SIGKILL信号是无法捕获的。  </p>
<h3 id="timeout-xian-zhi-yun-xing-shi-jian"><a class="toclink" href="#timeout-xian-zhi-yun-xing-shi-jian">timeout 限制运行时间</a></h3>
<p>当需要限制命令的运行时间的时候，可以用timeout命令：</p>
<div class="highlight"><pre><span></span><code>timeout [选项] 数字[后缀] 命令 [参数]...
</code></pre></div>

<p>后缀”s”代表秒(默认值)，”m”代表分，”h”代表小时，”d”代表天。  </p>
<p>如果程序超时则退出状态数为124，否则返回程序退出状态。<br>
如果没有指定信号则默认为TERM 信号。TERM 信号在进程没有捕获此信号时杀死进程。<br>
可以用 <code>-s, --signal</code> 指定要发送的信号。如果发送KILL(9)信号，强制退出，并且这个信号是无法被捕获的。<em>KILL(9)一定能退出，但是慎用。在别的信号无效的时候，考虑用这个终极信号。</em>  </p>
<p>time可以在启动脚本的命令前加上，或者写到脚本中，限制某一个子脚本的时间：</p>
<div class="highlight"><pre><span></span><code>timeout 300s script.sh
</code></pre></div>

<p>效果示例：</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">echo</span> <span class="s1">&#39;for i in {1..10}; do echo $i; sleep 1; done&#39;</span> &gt; script.sh
$ timeout 3s sh script.sh                                        
<span class="m">1</span>
<span class="m">2</span>
<span class="m">3</span>
$ <span class="nb">echo</span> <span class="nv">$?</span>
<span class="m">124</span>
$ 
</code></pre></div>

<p>执行了3秒就退出了，然后检查了一下退出状态码为124。  </p>
<h3 id="tui-chu-zhuang-tai-ma"><a class="toclink" href="#tui-chu-zhuang-tai-ma">退出状态码</a></h3>
<p>shell中运行的每个命令都使用<strong>退出状态码（exit status）</strong>。退出状态码是一个0~255的整数，在命令结束运行时有命令传给shell。  </p>
<h4 id="cha-kan-tui-chu-zhuang-tai-ma"><a class="toclink" href="#cha-kan-tui-chu-zhuang-tai-ma">查看退出状态码</a></h4>
<p>Linux提供了一个专门的变量 $? 来保存上一个已执行命令的退出状态码。对于需要进行检查的命令，必须在其运行完毕后立刻查看或使用 $? 变量。它的值会变成有shell所执行的最后一条命令的退出状态码。  </p>
<p>一个成功结束的命令的退出状态码是0。Linux错误退出状态码没有什么标准可循，但有一些可用的参考：</p>
<table>
<thead>
<tr>
<th>状态码</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>命令成功结束</td>
</tr>
<tr>
<td>1</td>
<td>一般性未知错误</td>
</tr>
<tr>
<td>2</td>
<td>不适合的shell指令</td>
</tr>
<tr>
<td>126</td>
<td>命令不可执行</td>
</tr>
<tr>
<td>127</td>
<td>没找到命令</td>
</tr>
<tr>
<td>128</td>
<td>无效的退出参数</td>
</tr>
<tr>
<td>128+x</td>
<td>与Linux信号x相关的严重错误</td>
</tr>
<tr>
<td>130</td>
<td>通过 Ctrl+C 终止的命令</td>
</tr>
<tr>
<td>255</td>
<td>正常范围之外的退出状态码</td>
</tr>
</tbody>
</table>
<p>&nbsp;<br>
举例说明：</p>
<ul>
<li>无效的命令会返回一个退出状态码127。</li>
<li>退出状态码126，表明用户没有执行命令的正确权限。</li>
<li>给命令提供了无效参数，会产生一般性的退出状态码1，表明在命令中发生了未知错误。</li>
</ul>
<h4 id="exit-ming-ling"><a class="toclink" href="#exit-ming-ling">exit 命令</a></h4>
<p>默认情况下，shell脚本会以脚本中的最后一个命令的退出状态码退出。<br>
使用exit命令可以改变这种行为，允许你在脚本结束时指定一个退出状态码。  </p>
<p>退出状态码是一个0~255的整数，参数太大，会被系统取模。  </p>
<h3 id="ifs-zi-duan-fen-ge-fu"><a class="toclink" href="#ifs-zi-duan-fen-ge-fu">IFS 字段分隔符</a></h3>
<p>环境变量IFS，叫做<strong>内部字段分隔符</strong>（internal field separator）。IFS环境变量定义了bash shell用作字段分隔符的一些列字符。默认情况下是空格、制表符、换行符。可以更改这个环境变量。  </p>
<h4 id="cha-kan-bian-liang-mo-ren-zhi"><a class="toclink" href="#cha-kan-bian-liang-mo-ren-zhi">查看变量默认值</a></h4>
<p>因为都是一些特殊字符，直接输出是看不到的：</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">echo</span> <span class="s2">&quot;</span><span class="nv">$IFS</span><span class="s2">&quot;</span>


$ <span class="nb">echo</span> <span class="s2">&quot;</span><span class="nv">$IFS</span><span class="s2">&quot;</span> <span class="p">|</span> od -b
<span class="m">0000000</span> <span class="m">040</span> <span class="m">011</span> <span class="m">012</span> <span class="m">012</span>
<span class="m">0000004</span>
$ 
</code></pre></div>

<p>这里转成了八进制。"040"是空格，"011"是制表符，"012"是换行符 。最后一个 012 是因为 echo 默认是会换行的。<br>
还可以试一下 -c 选项，这样会打印出 \t 和 \n 但是空格还是空格，就不明显了。  </p>
<p><strong>只识别换行符：</strong>  </p>
<div class="highlight"><pre><span></span><code><span class="nv">IFS</span><span class="o">=</span><span class="s1">$&#39;\n&#39;</span>
</code></pre></div>

<h5 id="jin-zhi-cha-kan-gong-ju"><a class="toclink" href="#jin-zhi-cha-kan-gong-ju">进制查看工具</a></h5>
<p>上面用到了进程查看工具，展开一下。上面用的是<strong>od命令</strong>。<br>
od命令系统默认的显示方式是八进制，名称源于Octal Dump。另外还有一个hexdump命令，以十六进制输出。功能上应该有一些差别，用起来差不多，这里的情况把od命令替换为hexdump命令的效果应该是一样的。<br>
hexdump有个-C（大写）选项是od没有的，这里没什么效果，用来查看文件的时候效果好。  </p>
<p>有三个命令都可以像UltraEdit那样以16进制的方式查看文件。<br>
hexdump的输入格式最灵活，因为它有专门的参数指定格式, 常用选项 -C。<br>
od没什么特点，od -x 和不带选项的xxd差不多。<br>
xxd还可以通过-r选项把16进制表示的反转换为正常的文件因为它默认的表示方式和UltraEdit最像：以位移开头，然后每两字节一组，每行最后有相应字节的文件显示。  </p>
<p>提到linux/unix下的进制查看工具，不得不提到以上的三个工具：</p>
<ul>
<li>hexdump在使用上和windows下的winhex接近。</li>
<li>xxd可以看作是od的升级产品。且其-r选项把16进制表示的反转换为正常的文件。比较像是ultraedit软件的十六进制查看。</li>
<li>od作为老牌的进制查看工具，使用上比较麻烦。且显示方式上不如上面两个工整。</li>
</ul>
<h4 id="lin-shi-geng-gai-ifs"><a class="toclink" href="#lin-shi-geng-gai-ifs">临时更改IFS</a></h4>
<p>一个可参考的安全实践是在改变IFS之前保存原来的值，之后再恢复它：</p>
<div class="highlight"><pre><span></span><code><span class="nv">IFS_old</span><span class="o">=</span><span class="nv">$IFS</span>
<span class="nv">IFS</span><span class="o">=</span><span class="s1">$&#39;\n&#39;</span>
&lt;使用新的IFS值&gt;
<span class="nv">IFS</span><span class="o">=</span><span class="nv">$IFS_old</span>
</code></pre></div>

<p>这就保证了在脚本的后续操作中使用的是IFS的默认值。建议在任何时候都这么做。  </p>
<h4 id="shi-yong-chang-jing"><a class="toclink" href="#shi-yong-chang-jing">使用场景</a></h4>
<p>在做字符串分隔的时候，可以尝试临时改变IFS变量。比如可以替换成按逗号分隔。<br>
演示效果如下：</p>
<div class="highlight"><pre><span></span><code>$ <span class="k">for</span> i <span class="k">in</span> <span class="s1">&#39;1,2,3&#39;</span><span class="p">;</span> <span class="k">do</span> <span class="nb">echo</span> <span class="nv">$i</span><span class="p">;</span> <span class="k">done</span>
<span class="m">1</span>,2,3
$ <span class="nv">IFS_old</span><span class="o">=</span><span class="nv">$IFS</span>
$ <span class="nv">IFS</span><span class="o">=</span><span class="s1">$&#39;,&#39;</span>
$ <span class="k">for</span> i <span class="k">in</span> <span class="s1">&#39;1,2,3&#39;</span><span class="p">;</span> <span class="k">do</span> <span class="nb">echo</span> <span class="nv">$i</span><span class="p">;</span> <span class="k">done</span>
<span class="m">1</span> <span class="m">2</span> <span class="m">3</span>
$ <span class="nv">IFS</span><span class="o">=</span><span class="nv">$IFS_old</span>
</code></pre></div>

<h4 id="zhi-ding-duo-ge-ifs-zi-fu"><a class="toclink" href="#zhi-ding-duo-ge-ifs-zi-fu">指定多个IFS字符</a></h4>
<p>要指定多个IFS字符，只要将它们在赋值行串起来就行了：</p>
<div class="highlight"><pre><span></span><code><span class="nv">IFS</span><span class="o">=</span><span class="s1">$&#39;\n&#39;</span>:<span class="p">;</span><span class="s2">&quot;</span>
</code></pre></div>

<p>这里将换行符、冒号、分号、双引号作为字段分隔符。  </p>
<h3 id="tee-jiang-shu-chu-tong-shi-fa-song-dao-ping-mu-he-wen-jian"><a class="toclink" href="#tee-jiang-shu-chu-tong-shi-fa-song-dao-ping-mu-he-wen-jian">tee 将输出同时发送到屏幕和文件</a></h3>
<p>tee命令相当于管道的一个T型接头。它将从STDIN过来的数据同时发往两处。一处是STDOUT，另一处是tee命令行所指定的文件名。<br>
如果后面再跟一个管道，就是一个存文件落盘，一个继续传递给下一个命令的STDIN。  </p>
<h4 id="xie-ru-dao-wen-jian"><a class="toclink" href="#xie-ru-dao-wen-jian">写入到文件</a></h4>
<p>默认情况下，每次都会覆盖文件：</p>
<div class="highlight"><pre><span></span><code>$ date <span class="p">|</span> tee test1
Fri Dec  <span class="m">6</span> <span class="m">16</span>:32:59 CST <span class="m">2019</span>
$ cat test1
Fri Dec  <span class="m">6</span> <span class="m">16</span>:32:59 CST <span class="m">2019</span>
$ date <span class="p">|</span> tee test1
Fri Dec  <span class="m">6</span> <span class="m">16</span>:33:10 CST <span class="m">2019</span>
$ cat test1
Fri Dec  <span class="m">6</span> <span class="m">16</span>:33:10 CST <span class="m">2019</span>
$ 
</code></pre></div>

<p>如果想追加数据，需要用-a选项：</p>
<div class="highlight"><pre><span></span><code>$ cat test1
Fri Dec  <span class="m">6</span> <span class="m">16</span>:33:10 CST <span class="m">2019</span>
$ date <span class="p">|</span> tee -a test1
Fri Dec  <span class="m">6</span> <span class="m">16</span>:34:00 CST <span class="m">2019</span>
$ cat test1
Fri Dec  <span class="m">6</span> <span class="m">16</span>:33:10 CST <span class="m">2019</span>
Fri Dec  <span class="m">6</span> <span class="m">16</span>:34:00 CST <span class="m">2019</span>
$ 
</code></pre></div>

<p>利用这个方法，既能将数据保存在文件中，也能将数据显示在屏幕上。这样就可以在为用户显示输出的同时再永久保存一份输出的内容了。  </p>
<h5 id="yong-guan-dao-fu-lian-jie-tee-ming-ling"><a class="toclink" href="#yong-guan-dao-fu-lian-jie-tee-ming-ling">用管道符连接tee命令</a></h5>
<p>上面的例子，是将输出同时写入文件和标准输出。对于标准输出的部分可以再加上管道符，这样就可以继续传递给下一下命令了。<br>
比如下面这样的用法：</p>
<div class="highlight"><pre><span></span><code>cmd1 <span class="p">|</span> tee out1.txt <span class="p">|</span> cmd2 <span class="p">|</span> tee out2.txt <span class="p">|</span> cmd3 &gt; out3.txt
</code></pre></div>

<p>管道符连接了3个命令，每一个命令执行的结果都可以记录到文件，并且还能同时传递给下一个命令。<br>
这个就是将中间结果落盘，以便查看。  </p>
<h3 id="jiao-ben-zhong-chuang-jian-wen-jian-cat-eof"><a class="toclink" href="#jiao-ben-zhong-chuang-jian-wen-jian-cat-eof">脚本中创建文件（cat EOF）</a></h3>
<p>一个比较常用的场景是，在脚本中创建配置文件。创建文件的同时，也写入文件内容。并且可以写的漂亮。  </p>
<h4 id="ji-ben-yong-fa"><a class="toclink" href="#ji-ben-yong-fa">基本用法</a></h4>
<p>创建文件，在这之后输入任何内容都是在文件里的。输入完成之后EOF结尾，代表结束：</p>
<div class="highlight"><pre><span></span><code><span class="gp">$ </span>cat &gt; <span class="nb">test</span> &lt;&lt; EOF
<span class="go">&gt; line 1,</span>
<span class="go">&gt; Line 2;</span>
<span class="go">&gt; LINE 3.</span>
<span class="go">&gt; EOF</span>
<span class="gp">$ </span>cat <span class="nb">test</span>
<span class="go">line 1,</span>
<span class="go">Line 2;</span>
<span class="go">LINE 3.</span>
<span class="gp">$ </span>
</code></pre></div>

<p>输出重定向将cat命令的输出写入文件（也可以用&gt;&gt;来追加内容）。输入不再取自标准输入，而是被重定向到命令行（或者脚本）之后的内容中。EOF符号标记了追加到文件中的数据的结束，<em>可以任意指定标记，这样避免和文件内容冲突</em>。<br>
使用这个命令，在脚本中创建一个文件并写入多行内容就很方便。  </p>
<h4 id="-eof"><a class="toclink" href="#-eof">&lt;&lt;- EOF</a></h4>
<p>使用EOF表示结束的时候，必须要顶格写，否则不认。如果在脚本中使用，有时候希望代码块有缩进。这时候就可以用这个：  </p>
<blockquote>
<p>If the redirection operator is &lt;&lt;-, then all leading tab characters are stripped from input lines and  the  line  containing  delimiter.   </p>
</blockquote>
<p>如果重定向的操作符是&lt;&lt;-，那么分界符（EOF）所在行的开头部分的制表符（Tab）都将被去除。  </p>
<p>不过这个只会忽略制表符，如果用空格缩进还是用不上。感觉很鸡肋。  </p>
<h4 id="zhi-jie-chuang-jian-jiao-ben-wen-jian-zhi-xing"><a class="toclink" href="#zhi-jie-chuang-jian-jiao-ben-wen-jian-zhi-xing">直接创建脚本文件执行</a></h4>
<p>执行命令可以用 <code>su USER -c "COMMAND"</code> 或者 <code>bash -c "COMMAND"</code> 这两个命令。这里-c后面的字符串将被当做命令来执行。如果命令简单，就直接写到最后COMMAND里就好了。<br>
如果命令有多行，可以用cat提高可读性。如果命令中用到很多引号，单双引号都有，也可以用cat，cat中的引号不用转义。<br>
具体形式如下，一定要使用双引号，因为头和尾的内容需要转义：</p>
<div class="highlight"><pre><span></span><code>bash -c <span class="s2">&quot;</span><span class="k">$(</span>cat <span class="s">&lt;&lt; EOF</span>
<span class="s">echo line 1</span>
<span class="s">echo line 2</span>
<span class="s">echo line 3</span>
<span class="s">EOF</span>
<span class="k">)</span><span class="s2">&quot;</span>
</code></pre></div>

<p>所有的内容都在双引号里，这样脚本中的内容也会被直接转义。可以给EOF加上单引号或双引号这样就不会转义文本的内容：</p>
<div class="highlight"><pre><span></span><code><span class="c1"># 下面两条命令看不出差别</span>
<span class="c1"># 这个是 echo 命令时才获取HOME的环境变量</span>
bash -c <span class="s2">&quot;</span><span class="k">$(</span>cat <span class="s">&lt;&lt; &#39;EOF&#39;</span>
<span class="s">echo $HOME</span>
<span class="s">EOF</span>
<span class="k">)</span><span class="s2">&quot;</span>
<span class="c1"># 这个是cat的时候就将HOME的环境变量给转义了，到echo只是输出一个普通的字符串</span>
bash -c <span class="s2">&quot;</span><span class="k">$(</span>cat <span class="s">&lt;&lt; EOF</span>
<span class="s">echo $HOME</span>
<span class="s">EOF</span>
<span class="k">)</span><span class="s2">&quot;</span>

<span class="c1"># 下面两条命令就能看出不同</span>
cat <span class="s">&lt;&lt; EOF</span>
<span class="s">echo $HOME</span>
<span class="s">EOF</span>

cat <span class="s">&lt;&lt; &#39;EOF&#39;</span>
<span class="s">echo $HOME</span>
<span class="s">EOF</span>
</code></pre></div>

<p>下面的脚本，先设置变量HOME，然后再将设置的HOME打印出来：</p>
<div class="highlight"><pre><span></span><code><span class="c1"># 正确的写法</span>
<span class="nv">HOME</span><span class="o">=</span><span class="nb">test</span> bash -c <span class="s2">&quot;</span><span class="k">$(</span>cat <span class="s">&lt;&lt; &#39;EOF&#39;</span>
<span class="s">echo $HOME</span>
<span class="s">EOF</span>
<span class="k">)</span><span class="s2">&quot;</span>
<span class="c1"># 错误的写法</span>
<span class="nv">HOME</span><span class="o">=</span><span class="nb">test</span> bash -c <span class="s2">&quot;</span><span class="k">$(</span>cat <span class="s">&lt;&lt; EOF</span>
<span class="s">echo $HOME</span>
<span class="s">EOF</span>
<span class="k">)</span><span class="s2">&quot;</span>
</code></pre></div>

<p>变量的设置仅在当前这条命令中有效，但是直接在后面用也是获取不到变量的值的。设置完成后，必须得运行一个子进程，才能在子进程中获取到开头设置的环境变量的值。  </p>
<p>下面是命令中有单双引号混用时使用的例子：</p>
<div class="highlight"><pre><span></span><code><span class="c1"># 先设置好环境变量，再执行命令，3条命令</span>
<span class="nv">PUBLIC_IP</span><span class="o">=</span><span class="s2">&quot;223.5.5.5&quot;</span> 
<span class="nv">PRIVATE_IP</span><span class="o">=</span><span class="s2">&quot;10.0.7.4&quot;</span>
sed -i <span class="s1">&#39;s|^\(\s*&quot;agentip&quot;: &quot;\)&#39;</span><span class="si">${</span><span class="nv">PUBLIC_IP</span><span class="si">}</span><span class="s1">&#39;\(&quot;,\)$|\1&#39;</span><span class="si">${</span><span class="nv">PRIVATE_IP</span><span class="si">}</span><span class="s1">&#39;\2|&#39;</span> /usr/local/gse/agent/etc/agent.conf

<span class="c1"># 直接设置变量并执行命令，1条命令</span>
<span class="nv">PUBLIC_IP</span><span class="o">=</span><span class="s2">&quot;223.5.5.5&quot;</span> <span class="nv">PRIVATE_IP</span><span class="o">=</span><span class="s2">&quot;10.0.7.4&quot;</span> bash -c <span class="s2">&quot;</span><span class="k">$(</span>cat <span class="s">&lt;&lt; &#39;EOF&#39;</span>
<span class="s">sed -i &#39;s|^\(\s*&quot;agentip&quot;: &quot;\)&#39;${PUBLIC_IP}&#39;\(&quot;,\)$|\1&#39;${PRIVATE_IP}&#39;\2|&#39; /usr/local/gse/agent/etc/agent.conf</span>
<span class="s">EOF</span>
<span class="k">)</span><span class="s2">&quot;</span>
</code></pre></div>

<h3 id="yong-jiu-zhong-ding-xiang"><a class="toclink" href="#yong-jiu-zhong-ding-xiang">永久重定向</a></h3>
<p>如果有大量的语句需要重定向，在每条命令后面都指定是相当麻烦的。<br>
下面的命令是，git放弃修改，强制覆盖本地代码：</p>
<div class="highlight"><pre><span></span><code>git fetch --all <span class="o">&amp;&amp;</span> git reset --hard origin/master <span class="o">&amp;&amp;</span> git pull
</code></pre></div>

<p>即使合并成一行，这里也是3条命令。如果要将输出重定向，仅仅在最后加上，则只会重定向最后一条命令的输出。  </p>
<h4 id="exec-ming-ling"><a class="toclink" href="#exec-ming-ling">exec 命令</a></h4>
<p>exec命令的效果是不产生新的子进程，所以不是做永久重定向的，但是靠这个命令能实现永久重定向。命令具体的效果是什么在这里不重要。<br>
主要看下面的代码：</p>
<div class="highlight"><pre><span></span><code><span class="nb">exec</span> <span class="m">1</span>&gt;/dev/null
git fetch --all <span class="o">&amp;&amp;</span> git reset --hard origin/master <span class="o">&amp;&amp;</span> git pull
</code></pre></div>

<p>这样，在exec命令之后，所有的语句就都会重定向到指定的地方。<br>
<strong>问题</strong>：已经重定向了STDOUT或STDERR，就很难在将它们重定向回原来的位置了。所以如果需要切换回来，重定向前先备份当前的描述符。  </p>
<h4 id="bao-cun-zhong-ding-xiang-wen-jian-miao-shu-fu"><a class="toclink" href="#bao-cun-zhong-ding-xiang-wen-jian-miao-shu-fu">保存重定向文件描述符</a></h4>
<p>可以将STDOUT的原来位置重定向到另外一个文件描述符，然后再利用该文件描述符重定向STDOUT：</p>
<div class="highlight"><pre><span></span><code><span class="nb">exec</span> <span class="m">3</span>&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="o">&amp;&amp;</span> <span class="nb">exec</span> <span class="m">1</span>&gt;/dev/null
<span class="c1"># 可以将下面的命令替换为echo打印到终端，方便的看到效果</span>
git fetch --all <span class="o">&amp;&amp;</span> git reset --hard origin/master <span class="o">&amp;&amp;</span> git pull
<span class="nb">exec</span> <span class="m">1</span>&lt;<span class="p">&amp;</span><span class="m">3</span>
</code></pre></div>

<p>先将 FD3 指向 STDOUT，指向 FD3 就是指向 STDOUT。然后就可以把 FD1 改了。之后，要再将 FD1 重新指向 STDOUT，指向 FD3 就是指向 STDOUT，所以通过 FD3 就又指回去了。  </p>
<p>另外，下面的方法效果也是一样的：</p>
<div class="highlight"><pre><span></span><code><span class="nb">exec</span> <span class="m">4</span>&lt;<span class="p">&amp;</span><span class="m">1</span> <span class="o">&amp;&amp;</span> <span class="nb">exec</span> <span class="m">1</span>&gt;/dev/null
git fetch --all <span class="o">&amp;&amp;</span> git reset --hard origin/master <span class="o">&amp;&amp;</span> git pull
<span class="nb">exec</span> <span class="m">1</span>&lt;<span class="p">&amp;</span><span class="m">4</span>
</code></pre></div>

<p>先将 STDOUT 指向 FD4，这样就把 STDOUT 备份到 FD4了。之后再加备份的 FD4 里的 STDOUT 指回给 FD1。  </p>
<h3 id="chuang-jian-ben-di-lin-shi-wen-jian-mktemp"><a class="toclink" href="#chuang-jian-ben-di-lin-shi-wen-jian-mktemp">创建本地临时文件（mktemp）</a></h3>
<p>在需要临时将内容保存到文件的时候，有个特殊命令可以用来创建临时文件。mktemp命令可以在/tmp目录中创建一个唯一的临时文件。shell会创建这个文件，但不用默认的umask值。它会将当前用户设置为文件的属主，并且只有属主有读写权限。  </p>
<h4 id="chuang-jian-wen-jian"><a class="toclink" href="#chuang-jian-wen-jian">创建文件</a></h4>
<p>默认情况下，mktemp会在当前目录中创建一个文件。使用命令的时候，需要指定一个文件名模板。模板可以包含任意文本文件名，在文件名末尾加上6个X（几个X都没关系，书上建议6位）：</p>
<div class="highlight"><pre><span></span><code>$ mktemp test1.XXXXXX
test1.Acrugq
$ ls -al test1*
-rw-------. <span class="m">1</span> steed steed <span class="m">0</span> 12月 <span class="m">12</span> <span class="m">14</span>:26 test1.Acrugq
$ 
</code></pre></div>

<p>mktemp命令会用6位字符码替换这6个X，从而保证文件名在目录中是唯一的。  </p>
<h4 id="zai-jiao-ben-zhong-shi-yong"><a class="toclink" href="#zai-jiao-ben-zhong-shi-yong">在脚本中使用</a></h4>
<p>在脚本中使用mktemp命令时，需要将文件名保存到变量中，这样就能在后面的脚本中引用了：</p>
<div class="highlight"><pre><span></span><code>$ mktemp test2.XXXXXX
test2.leOFBZ
$ mktemp test2.XXXXXX
test2.5kDbKn
$ mktemp test2.XXXXXX
test2.domeOC
$ mktemp test2.XXXXXX
test2.CJX702
$ <span class="nv">tempfile</span><span class="o">=</span><span class="k">$(</span>mktemp test2.XXXXXX<span class="k">)</span>
$ <span class="nb">exec</span> <span class="m">3</span>&gt;&gt;<span class="nv">$tempfile</span>
$ <span class="nb">echo</span> <span class="s2">&quot;test2 Line1&quot;</span> &gt;<span class="p">&amp;</span><span class="m">3</span>
$ <span class="nb">echo</span> <span class="s2">&quot;TEST2 LINE2&quot;</span> &gt;<span class="p">&amp;</span><span class="m">3</span>
$ <span class="nb">exec</span> <span class="m">3</span>&gt;<span class="p">&amp;</span>-
$ cat <span class="nv">$tempfile</span>
test2 Line1
TEST2 LINE2
$ ls -al test2*
-rw-------. <span class="m">1</span> steed steed  <span class="m">0</span> 12月 <span class="m">12</span> <span class="m">14</span>:41 test2.5kDbKn
-rw-------. <span class="m">1</span> steed steed  <span class="m">0</span> 12月 <span class="m">12</span> <span class="m">14</span>:41 test2.CJX702
-rw-------. <span class="m">1</span> steed steed  <span class="m">0</span> 12月 <span class="m">12</span> <span class="m">14</span>:41 test2.domeOC
-rw-------. <span class="m">1</span> steed steed  <span class="m">0</span> 12月 <span class="m">12</span> <span class="m">14</span>:41 test2.leOFBZ
-rw-------. <span class="m">1</span> steed steed <span class="m">24</span> 12月 <span class="m">12</span> <span class="m">14</span>:41 test2.tMVTwN
$ rm -f <span class="nv">$tempfile</span> <span class="m">2</span>&gt; /dev/null
$ ls -al test2*
-rw-------. <span class="m">1</span> steed steed <span class="m">0</span> 12月 <span class="m">12</span> <span class="m">14</span>:41 test2.5kDbKn
-rw-------. <span class="m">1</span> steed steed <span class="m">0</span> 12月 <span class="m">12</span> <span class="m">14</span>:41 test2.CJX702
-rw-------. <span class="m">1</span> steed steed <span class="m">0</span> 12月 <span class="m">12</span> <span class="m">14</span>:41 test2.domeOC
-rw-------. <span class="m">1</span> steed steed <span class="m">0</span> 12月 <span class="m">12</span> <span class="m">14</span>:41 test2.leOFBZ
$ 
</code></pre></div>

<p>这里先创建了几个临时文件，干扰一下。使用的时候，创建了文件描述符3来使用，使用完之后关闭了文件描述符。最后精确的把用完的临时文件给删除了。  </p>
<h4 id="zai-tmp-mu-lu-chuang-jian-lin-shi-wen-jian"><a class="toclink" href="#zai-tmp-mu-lu-chuang-jian-lin-shi-wen-jian">在/tmp目录创建临时文件</a></h4>
<p>-t选项会强制mktemp命令在系统的临时目录创建文件。此时返回的就是创建的文件的全路径：</p>
<div class="highlight"><pre><span></span><code>$ mktemp -t test3.XXXXXX
/tmp/test3.aPIXIy
$ 
</code></pre></div>

<h4 id="chuang-jian-lin-shi-mu-lu"><a class="toclink" href="#chuang-jian-lin-shi-mu-lu">创建临时目录</a></h4>
<p>-d选项则是创建一个临时目录。如果要在/tmp目录下创建临时目录，就是-dt：</p>
<div class="highlight"><pre><span></span><code>$ mktemp -d dir.XXXXXX
dir.aBDmsd
$ mktemp -dt dir.XXXXXX
/tmp/dir.pqW927
$ 
</code></pre></div>
      </div>
      <div class="back-to-top">
        <a href="/">HOME</a>
        <a href="#top">TOP</a>
      </div>
    </article>
  </div>
<!-- end article -->
<!-- 页面往下滚动一段之后才会显示TOC -->
<script>
  window.onscroll = function() {
    var tocbox = document.getElementsByClassName('toc')[0];
    var osTop = document.documentElement.scrollTop || document.body.scrollTop;
    var osWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
    // console.log(osTop)
    if (osTop>300 && osWidth>865) {
      tocbox.style.display = "block"
    }
    if (osTop<300 || osWidth<865) {
      tocbox.style.display = "none"
    }
  }
</script>
                <footer>
                    <div class="icons">
                    </div>
                    <span id="busuanzi_container_page_pv" style="padding: 10px">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
                    <span id="busuanzi_container_site_pv" style="padding: 10px">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
                    <span id="busuanzi_container_site_uv" style="padding: 10px">本站总访客数<span id="busuanzi_value_site_uv"></span>人</span>
                    <p>© <script>document.write(moment().format('YYYY'));</script> 749B</p>
                </footer>
        </div>
</body>
</html>